package MusicLandscape.entities;

import java.util.Arrays;

/**
 * Represents a concert of a certain artist with a certain set list as a specific event.
 * <p>
 * As an extension of a generic event a concert provides the possibility to store the setlist.
 * The setlist is the sequence of non-null tracks played at a concert.
 * Class concert provides methods to add tracks to the (end of the) tracklist and to reset the tracklist
 * all together (empty it).
 *
 * @author Jonas Altrock (ew20b126@technikum-wien.at)
 * @version 1
 * @since ExerciseSheet03
 */
public class Concert extends Event {
    /**
     * next free index
     */
    private int nextIdx = 0;

    /**
     * array holding the tracks of the setlist
     */
    private Track[] setList;

    public Concert() {
        setList = new Track[1];
    }

    /**
     * adds a track to the set list
     * <p>
     * Tracks are added to the end of the list with the first track played at the concert being stored at the
     * beginning of the list.
     * This method returns whether the non-null track was successfully added to the setlist or not.
     * This method does not accept/ignores null tracks.
     *
     * @param t the track to add
     * @return true if the track was added, false otherwise
     */
    public boolean addTrack(Track t) {
        if (t == null) {
            return false;
        }

        ensureCapacity(nextIdx + 1);
        setList[nextIdx++] = t;

        return true;
    }

    /**
     * ensures sufficient storage for a specific number of tracks in the setlist
     * <p>
     * If the requested capacity can not be ensured before the call, this method increases storage thereby
     * keeping all existing entries.
     *
     * @param length the maximum number of tracks this concert must be able to keep in the setlist
     */
    private void ensureCapacity(int length) {
        if (length <= setList.length) {
            return;
        }

        setList = Arrays.copyOf(setList, length);
    }

    /**
     * gets the setlist
     * <p>
     * This method returns a defensive copy, meaning it returns a copy of the setlist, which contains (deep) copies of
     * the tracks in the setlist. The returned array does not contain any null entries. If the setlist is empty an
     * array of length 0 is returned.
     *
     * @return the setlist of this concert
     */
    public Track[] getSetList() {
        Track[] tracks = new Track[nextIdx];

        for (int i = 0; i < nextIdx; i++) {
            tracks[i] = new Track(setList[i]);
        }

        return tracks;
    }

    /**
     * sets the setList
     * <p>
     * This method creates a defensive copy, meaning it sets the setlist of this concert to contain (deep copies of)
     * all non-null tracks of the argument (and only those) thereby preserving the relative ordering of entries.
     * Null entries in the argument are ignored and not part of the resulting setlist.
     * A null argument is generally ignored.
     *
     * @param tracks the tracks for the setlist
     */
    public void setSetList(Track[] tracks) {
        if (tracks == null) {
            return;
        }

        setList = new Track[tracks.length];
        nextIdx = 0;

        for (Track t : tracks) {
            if (t == null) {
                continue;
            }
            addTrack(new Track(t));
        }
    }

    /**
     * removes all tracks from the setlist
     */
    public void resetSetList() {
        nextIdx = 0;
        Arrays.fill(setList, null);
    }

    /**
     * get the length of the playlist the length of the playlist is the number of entries in the setlist.
     *
     * @return the number of tracks in the setlist
     */
    public int nrTracks() {
        return nextIdx;
    }

    /**
     * calculates the total duration (in seconds) of all tracks in the setlist
     * <p>
     * More specifically the method returns an estimation (lower bound) since tracks with unknown duration are treated having duration 0.
     *
     * @return the total duration of the setlist in seconds
     */
    public int duration() {
        return Arrays.stream(getSetList()).map(Track::getDuration).reduce(Integer::sum).orElseGet(() -> 0);
    }

    /**
     * returns the impact of this event
     * <p>
     * the impact is an estimation of the number of people who took notice of this event. For a concert,
     * the impact is calculated from the number of attendees and the length of the concert. The number of
     * attendees is multiplied by the duration factor, which is initially 1 but increases by one for every
     * started half hour the concert lasts.
     * E.G: 400 people attending the concert. 75 minutes duration; duration factor=3
     * (two full half hours, plus one started half hour) impact therefore is 400*3.
     *
     * @return the impact
     */
    @Override
    public int impact() {
        int factor = 1 + (duration() / 1800);
        return getAttendees() * factor;
    }

    /**
     * returns a String representation of this concert
     * <p>
     * the string representation of a concert appends the following line to the string representation
     * of a generic event (without quotes):
     *
     * <pre>
     * "number of tracks" tracks played, total duration "time".
     * </pre>
     * <p>
     * time is displayed in the format hh:mm with leading zeros
     *
     * @return the string representation
     */
    @Override
    public String toString() {
        int min = duration() / 60;
        String hours = String.format("%02d", (min / 60) % 100);
        String minutes = String.format("%02d", min % 60);

        return super.toString() + "\n" +
                nrTracks() + " tracks played, total duration " + hours + ":" + minutes + ".";
    }
}
